package androidgames.networkgames.network;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList;

import android.os.Handler;
import android.util.Log;

public class ConnectionHandler implements Runnable{
	public static interface OnDataReceiveListener{
		public abstract void onDataReceive(byte[] data) throws IOException;
	}
	
	private int TIMEOUT = 5000;
	
	private ArrayList<OnDataReceiveListener> listenerList = new ArrayList<ConnectionHandler.OnDataReceiveListener>();
	public volatile boolean bRunning = false;
	private boolean bIsConnected = false;
	private Socket socket = null;
	private Thread receiveThread = null;
	private DataOutputStream streamOut = null;
	private InputStream inputStream = null;
	
	private String strIP = "192.168.0.100";
	private int iPort = 7777;
	
	Handler handler = new Handler();
	
	public void connectToServer(String strIP, int iPort) throws SocketTimeoutException, IllegalArgumentException, IOException {
		if(bIsConnected){
			return;
		}
		
		this.strIP = strIP;
		this.iPort = iPort;
		
		socket = new Socket();
		try{
			socket.connect(new InetSocketAddress(strIP, iPort), TIMEOUT);
		}
		catch (SocketTimeoutException e) {
			throw e;
		}
		catch (IllegalArgumentException e) {
			throw e;
		}
		catch (IOException e) {
			throw e;
		}
		bRunning = true;
		bIsConnected = true;
		inputStream = socket.getInputStream();
		streamOut = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
		receiveThread = new Thread(this);
		receiveThread.start();
	}
	
	public void addEventListener(OnDataReceiveListener listener){
		listenerList.add(listener);
	}
	
	public void removeEventListener(OnDataReceiveListener listener){
		listenerList.remove(listener);
	}
	
	private void fireDataReceivedEvent(byte[] data) throws IOException{
		for(OnDataReceiveListener listener : listenerList){
			listener.onDataReceive(data);
		}
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(bRunning){
			try {
				if(socket.getInputStream().available() > 0){
				    byte[] data = receiveFixedData(inputStream, 4);
				    if(data == null){
				        continue;
				    }
				    
				    //get message length
				    ByteArrayInputStream BAIS = new ByteArrayInputStream(data);
				    DataInputStream DIS = new DataInputStream(BAIS);
				    
				    int messageLength = DIS.readInt();
				    if(messageLength > 0){
				        data = receiveFixedData(inputStream, messageLength);
				        if(data == null){
				            continue;
				        }
				    } 
				    fireDataReceivedEvent(data);
				}
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
            	Log.d("Exception",ex.toString());
            }
		}
	}

	private byte[] receiveFixedData(InputStream _inputStream, int count) throws Exception {
		// TODO Auto-generated method stub
		//Check the data
        if (count <= 0)
        {
            throw new Exception("Parameter's value cannot be 0 or less than 0.");
        }

        //set the buffer with the input length
        byte[] buffer = new byte[count];
        int receivedCount;
        int position = 0;
        // read until all data is received.
        do
        {
            //read the stream to buffer
            receivedCount = _inputStream.read(buffer, position, count - position);
            if (receivedCount == 0)
            {
                return null;
            }
            position += receivedCount;
        } while (position < count);
        return buffer;
	}
	
	public void send(byte[] data) throws IOException{
		streamOut.write(intToByteArray(data.length));
		streamOut.flush();
		streamOut.write(data);
		streamOut.flush();
	}
	
	private final byte[] intToByteArray(int value) {
        return new byte[] {
                (byte)(value >>> 24),
                (byte)(value >>> 16),
                (byte)(value >>> 8),
                (byte)value};
    }
	
	public void close(){
		bRunning = false;
		try {
			receiveThread.join();
			socket.close();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
